package flock;

import generics.AbstractGroup;
import generics.AbstractGroupMember;
import generics.AbstractSprite;
import gui.Canvas;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.util.List;

/**
 * This class implements a member of flock that exhibits "leaderless-flock
 * behavior," through rules of cohesion, separation and alignment that determine
 * how a FlockMember moves within it's Flock
 * 
 * @author Tom Marmaduke
 * 
 */
public class FlockMember extends AbstractGroupMember {

	private double myMaxSpeed;
	private int mySeparationWeight = 6;
	private int myCohesionWeight = 1;
	private int myAlignmentWeight = 1;

	public FlockMember(AbstractGroup flock, Point center, Dimension size,
			Point velocity, Color color, double speed, String type) {
		super(flock, center, size, velocity, color, type, 0);
	}

	/**
	 * this method determines the necessary acceleration for the flock member to
	 * obey the rules of flocking determines this by iterating through the other
	 * flock members, checking their position, and calculating the necessary
	 * acceleration to maintain separation and alignment. It also locates the
	 * center of the flock (within a certain distance) and creates an
	 * acceleration component pointing there. These accelerations are weighted
	 * and summed to get a net acceleration.
	 */
	public Point getAcceleration(List<AbstractGroup> list, double sep,
			double groupsep, double aligntol) {
		Point separationAccel = new Point(0, 0);
		int separationCount = 0;
		Point alignAccel = new Point(0, 0);
		int alignCount = 0;
		Point flockCenter = new Point(0, 0);
		int flockCount = 0;

		// For every bird in the system, check distances and generate necessary
		// acceleration
		for (AbstractGroup group : list) {
			if (getGroup().equals(group)) {
				for (AbstractGroupMember member : group.getMembers()) {
					if (member.getType().equals(getType())) {
						double d = getDistance(member);
						getSeparationAcceleration(sep, separationAccel,
								separationCount, member, d);
						getAlignmentAcceleration(aligntol, alignAccel,
								alignCount, member, d);
						sumFlockMemberLocations(flockCenter, flockCount,
								member, d);
					}
				}
			} else {
				for (AbstractGroupMember member : group.getMembers()) {
					double d = getDistance(member);
					getSeparationAcceleration(groupsep, separationAccel,
							separationCount, member, d);
				}
			}
		}
		weightAcceleration(separationAccel, separationCount);
		weightAcceleration(alignAccel, alignCount);
		weightAcceleration(flockCenter, flockCount);
		Point cohesacc = getCohesionAcceleration(flockCenter);
		return sumAccelerations(separationAccel, alignAccel, cohesacc);
	}

	/**
	 * Takes the coordinate of the flock's center and generates a unit vector in
	 * that direction.
	 */
	private Point getCohesionAcceleration(Point flockcenter) {
		Point cohesacc = new Point(0, 0);
		Point desiredvel = new Point(flockcenter.x - getCenter().x,
				flockcenter.y - getCenter().y);
		double d = getDistance(flockcenter);
		if (d > 0) {
			cohesacc.setLocation(Math.ceil(desiredvel.x / d), Math
					.ceil(desiredvel.y / d));
		}
		return cohesacc;
	}

	/**
	 * Sums the three acceleration Points to get a net acceleration
	 */
	private Point sumAccelerations(Point sepacc, Point alignacc, Point cohesacc) {
		Point acc = new Point();
		acc.x = mySeparationWeight * sepacc.x + myAlignmentWeight * alignacc.x
				+ myCohesionWeight * cohesacc.x;
		acc.y = mySeparationWeight * sepacc.y + myAlignmentWeight * alignacc.y
				+ myCohesionWeight * cohesacc.y;
		return acc;
	}

	/**
	 * averages the accelerations. This weights the accelerations so that each
	 * bird gets the same "pull"
	 * 
	 */
	private void weightAcceleration(Point sepacc, int sepcount) {
		if (sepcount > 0) {
			sepacc.setLocation(Math.ceil(sepacc.x / sepcount), Math
					.ceil(sepacc.y / sepcount));
		}
	}

	/**
	 * Generates a unit vector away from a bird that is too close. It is
	 * weighted by distance (smaller distance = bigger accel), and added to a
	 * general separation accel Point
	 * 
	 */
	// Separation
	private void getSeparationAcceleration(double sep, Point sepacc,
			int sepcount, AbstractGroupMember other, double d) {
		// If the distance is greater than 0 and less defined amount
		if ((d > 0) && (d < sep)) {
			// Calculate vector pointing away from neighbor
			Point away = new Point(getCenter().x - other.getCenter().x,
					getCenter().y - other.getCenter().y);
			double mag = away.distance(0, 0);
			away.setLocation(Math.ceil(away.x / mag / d), Math.ceil(away.y
					/ mag / d)); // normalize vector and weigh it by distance
			sepacc.translate(away.x, away.y);
			sepcount++;
		}
	}

	/**
	 * Adds the velocity of another bird to the align acceleration
	 */
	// Alignment
	private void getAlignmentAcceleration(double aligntol, Point alignacc,
			int aligncount, AbstractGroupMember other, double d) {
		if ((d > 0) && (d < aligntol)) {
			Point othervel = other.getVelocity();
			alignacc.translate(othervel.x, othervel.y);
			aligncount++;
		}
	}

	/**
	 * Adds the location of the other bird within range
	 */
	// Cohesion
	private void sumFlockMemberLocations(Point flockCenter, int flockcount,
			AbstractGroupMember other, double d) {
		Point otherloc = other.getCenter();
		flockCenter.translate(otherloc.x, otherloc.y); // Add location
		flockcount++;
	}

	/**
	 * Calculates acceleration and alter the velocity based on it. Also limits
	 * the velocity
	 */
	public void updateVelocity(List<AbstractGroup> list, int sep, int groupsep,
			int aligntol) {
		Point accel = getAcceleration(list, sep, groupsep, aligntol);
		getVelocity().x += accel.x;
		getVelocity().y += accel.y;
		limitVelocity();
	}

	/**
	 * Updates parameters of the flock member given a set of values
	 */
	public void updateParameters(List<AbstractGroup> list, Flock flock,
			int sep, int groupsep, int ali, Dimension size, int maxspeed,
			Color bc) {
		setGroup(flock);
		setSize(size);
		myMaxSpeed = maxspeed;
		setColor(bc);
		updateVelocity(list, sep, groupsep, ali);
	}

	/**
	 * scales the velocity to ensure that it doesn't exceed the max speed
	 */
	private void limitVelocity() {
		double speed = Math.sqrt(Math.pow(getVelocity().x, 2)
				+ Math.pow(getVelocity().y, 2));
		if (speed > myMaxSpeed) {
			getVelocity().x = (int) (myMaxSpeed * getVelocity().x / speed);
			getVelocity().y = (int) (myMaxSpeed * getVelocity().y / speed);
		}
	}

	/**
	 * Describes how to animate the flock member.
	 */
	public void update(Canvas canvas, int sep, int groupsep, int aligntol) {
		updateVelocity(canvas.getGroups(), sep, groupsep, aligntol);
		super.update(canvas);
		wrap(canvas.getSize());
	}

	/**
	 * paints the flockmember
	 */
	public void paint(Graphics pen) {
		// paint trail
		super.paint(pen);
		// paint a rectangle for the member
		// hopefully we can support use of Image with drawImage
		pen.setColor(getColor());
		pen.fillRect(getCenter().x - getSize().width / 2, getCenter().y
				- getSize().height / 2, getSize().width, getSize().height);
	}
}
